/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package replica.transaction.stateMachine;

import java.util.logging.Logger;
import replica.ReplicaLoggerFactory;
import replica.stableMemory.StableFile;
import replica.transaction.Operation;
import replica.transaction.ReplicaEntityInterface.Expected;
import replica.transaction.ReplicaManagerLogRegister;
import replica.transaction.TransactionException;
import replica.transaction.TransactionGetListMsg;
import replica.transaction.TransactionStatus;
import replica.transaction.TransactionStatus.Type;

/**
 *
 * @author andrea
 */
public class ReplicaManagerStateMachine implements StateMachine
{
    TransactionStatus myStatus;
    long nodeId;
    long replicaHostId;
    private Logger logger,transitionLogger;
    private ReplicaManagerLogRegister logRegister;
    private StableFile<TransactionStatus> stableFile;
    
    public ReplicaManagerStateMachine(long nodeId,long replicaHostId)
    {
        this.nodeId=nodeId;
        this.replicaHostId=replicaHostId;
        this.logger=ReplicaLoggerFactory.getReplicaManagerLogger(this.nodeId, this.replicaHostId,0);
        this.transitionLogger=ReplicaLoggerFactory.getReplicaManagerStateMachineLogger(this.nodeId, this.replicaHostId,0);
        this.stableFile=new StableFile<TransactionStatus>("files/LogRegister/ReplicaManager_"+this.nodeId+"_"+this.replicaHostId+"/");
        this.logRegister=new ReplicaManagerLogRegister(this.nodeId,this.stableFile,this.replicaHostId);
    }
    
    @Override
    public void initializeStatus()
    {
        //TODO 
        //SE c'e' un logRegister register gia' su file 
        //(significa che il ReplicaManager ha avuto una precedente vita)
        //          allora crea TransactionStatus da file
        //ALTRIMENTI
        //          crea il TransactionStatus da capo
        TransactionStatus status= retrieveTransactionStatus(nodeId,replicaHostId);
        if(status==null)
            myStatus=TransactionStatus.buildOKStatus(nodeId, 0);
        else
            myStatus=status;
    }
    
    
    
    @Override
    public TransactionStatus getStatus() {
        return this.myStatus;
    }

    @Override
    public void moveToStatus(Type newStatusType, Operation operation) 
    {
        //Ci sono controlli sovrabbondanti per questioni di debug
        
        try{
            //PER DEBUG
            if (myStatus==null || myStatus.getType()==null || myStatus.getSerial()==null)
                throw new NullPointerException("myStatus"+myStatus+
                        "; myStatus.getType()="+myStatus.getType()+"; statusMsg"+newStatusType+
                        "; myStatus.getSerial()"+myStatus.getSerial()
                        
                        );
            //FINE PER DEBUG
            
            
            switch(newStatusType)
            {
                case READY:
                    if (
                            myStatus.getType()!=TransactionStatus.Type.OK || 
                            (
                                (
                                    operation.getOperationType()==Operation.OperationType.ENQUEUE ||
                                    operation.getOperationType()==Operation.OperationType.DEQUEUE
                                )&&
                                !operation.getSerialNumber().equals(myStatus.getSerial()+1)
                            )
                    )
                        throw new InvalidTransitionException(myStatus,newStatusType,this.nodeId);
                    this.myStatus=TransactionStatus.buildStateMachineStatus(nodeId, operation, newStatusType);
                    logRegister.write(myStatus);
                    break;
                        
                case COMMIT:
                    if (
                            myStatus.getType()==TransactionStatus.Type.READY &&
                            myStatus.getSerial().equals(operation.getSerialNumber() )
                       )
                    {
                        this.myStatus=TransactionStatus.buildStateMachineStatus(nodeId, operation, newStatusType);
                        logRegister.write(myStatus);
                        
                        //PER DEBUG
                        
                        //FINE PER DEBUG
                    }
                    //PER DEBUG
                    else
                    {
                        System.out.println("myStatus.getType()==TransactionStatus.Type.READY="+
                                (myStatus.getType()==TransactionStatus.Type.READY )+
                                "; myStatus.getSerial()==operation.getSerialNumber()="+
                                (myStatus.getSerial()==operation.getSerialNumber() )+
                                "; myStatus.getSerial()="+myStatus.getSerial()+
                                "; operation.getSerialNumber()="+operation.getSerialNumber()
                                );
                        throw new InvalidTransitionException(myStatus,newStatusType,this.nodeId);
                    }
                    //FINE PER DEBUG
                    break;
                    
                    
                case OK:
                    if (
                                myStatus.getType()==TransactionStatus.Type.COMMIT //&& 
                                //myStatus.getSerial().equals(operation.getSerialNumber())
                       )
                    {
                        //Il serial rimane quello del commit
                        this.myStatus=TransactionStatus.buildOKStatus(nodeId, myStatus.getSerial());
                        logRegister.write(myStatus);
                    }
                                
                    
                    else if(
                                myStatus.getType()==TransactionStatus.Type.READY && 
                                myStatus.getSerial().equals(operation.getSerialNumber())
                            )
                    {
                        //The transaction has been aborted
                        this.myStatus=TransactionStatus.buildOKStatus(nodeId, myStatus.getSerial()-1);
                        logRegister.write(myStatus);
                    }
                    
                        
                    else if ( myStatus.getType()==TransactionStatus.Type.MYSERIAL)
                    {
                        this.myStatus=TransactionStatus.buildOKStatus(nodeId, myStatus.getSerial());
                        logRegister.write(myStatus);
                    }
                    
                    else
                        throw new InvalidTransitionException(myStatus,newStatusType,this.nodeId);
                    break;
                    
                case MYSERIAL:
                    if (myStatus.getType()==TransactionStatus.Type.OK)
                        this.myStatus=TransactionGetListMsg.build_MYSERIAL_msg(
                                nodeId, replicaHostId, myStatus.getSerial());
                    break;
                    
                default:
                        throw new InvalidTransitionException(myStatus,newStatusType,this.nodeId);
            }
            
            this.transitionLogger.info("I am on status "+this.myStatus);
        }catch(InvalidTransitionException ise)
        {ise.printStackTrace();System.exit(-8732);}
    }

    @Override
    public Expected amIWaitingFor(TransactionStatus statusMsg) 
    {
        
        Expected returnedValue=Expected.NO;
        try{
            //PER DEBUG
            if (myStatus==null || myStatus.getType()==null)
                throw new NullPointerException("myStatus"+myStatus+
                        "; myStatus.getType()="+myStatus.getType()+"; statusMsg"+statusMsg
                        
                        );
            if (statusMsg.getType()==TransactionStatus.Type.ABORT &&
                    (
                        statusMsg.getSerial()<this.getStatus().getSerial() ||
                        (
                            statusMsg.getSerial()==this.getStatus().getSerial() &&
                            this.getStatus().getType()==TransactionStatus.Type.ABORT
                        )
                    )
               )
                throw new TransactionException("ERRORE: io sono in "+this.getStatus()+
                        " e ho ricevuto "+statusMsg+". Questo e' impossibile.");
                
            //FINE PER DEBUG
            
            switch(statusMsg.getType())
            {
                case PREPARE:
                    if (
                            myStatus.getType()==TransactionStatus.Type.OK && 
                            (
                                statusMsg.getOperation().getOperationType()==Operation.OperationType.ENQUEUE ||
                                statusMsg.getOperation().getOperationType()==Operation.OperationType.DEQUEUE
                            ) &&
                            statusMsg.getSerial().equals( myStatus.getSerial()+1 )
                       )
                        returnedValue=  Expected.YES;
                    else if (
                            myStatus.getType()==TransactionStatus.Type.OK && 
                            statusMsg.getOperation().getOperationType()==Operation.OperationType.ALIGNMENT  &&
                            statusMsg.getSerial().equals( myStatus.getSerial())
                            )
                        returnedValue=  Expected.YES;  
                    else
                        returnedValue= Expected.NO;
                    break;

                case COMMIT://The same as ABORT
                case ABORT:
                    //Se sono in NOT_READY posso ignorare questo messaggio
                    if (
                            myStatus.getType()==TransactionStatus.Type.READY && 
                            statusMsg.getSerial().equals(myStatus.getSerial())
                       )
                        returnedValue=Expected.YES;
                    else
                        returnedValue=Expected.MAYBE_REPLICATED_DECISION;
                    break;


                case GETLIST_REQ:
                    if (myStatus.getType()==TransactionStatus.Type.OK)
                        returnedValue=Expected.YES;
                    else
                        returnedValue=Expected.NO;
                    break;

                case IWANT_LIST:
                    if(
                            myStatus.getType()==TransactionStatus.Type.MYSERIAL &&
                            myStatus.getSerial().equals(statusMsg.getSerial() )
                      )
                    {
                        if (statusMsg.getReplicaHostId()==this.replicaHostId)
                            //FrontEnd wants the list from me
                            returnedValue=Expected.YES;
                        else
                            //FrontEnd wants a list from another ReplicaManager
                            returnedValue=Expected.NOT_FOR_ME;
                    }else
                        returnedValue=Expected.NO;
                    break;
                    
                case ACK:
                    if (myStatus.getType()==TransactionStatus.Type.MYSERIAL)
                        returnedValue=Expected.YES;
                    else
                        returnedValue=Expected.NO;
                    break;
                    
                //PER DEBUG
                default:
                    throw new TransactionException("ERRORE: non posso ricevere un messaggio di tipo "
                                +statusMsg);

                //FINE DEBUG
            }
            if (returnedValue!=Expected.YES)
                logger.warning("I am not waiting for this message. I will ignore it. My status is "+
                        this.myStatus);
        }catch(Exception e){e.printStackTrace();}
        return returnedValue;
    }
    
    
    public TransactionStatus retrieveTransactionStatus(long replicaHostId, long NodeId)
    {
        TransactionStatus status=null;
        try{
            status=stableFile.read(TransactionStatus.class);
            if(status!=null){
                System.out.println("!!!NewStatus="+status.toString());
                return status;
            }
        }
            catch(Exception e){
            e.printStackTrace();
            System.exit(-93883);
            }
        return status;
            
        }
    
}
